Перейти к основному содержимому

5.02. Django

Разработчику Архитектору

Django

Что такое Django?

Django — это свободный, высокий, серверный (бэкенд) веб-фреймворк, написанный на языке программирования Python и распространяемый под лицензией BSD. Его ключевой принцип — «разработка, ориентированная на повторное использование и „не повторяй себя“ (DRY)». Django не является микросервисной платформой или узкоспециализированным решением: он представляет собой монолитный, полный стек, предназначенный для быстрого создания веб-приложений со сложной логикой, строгой структурой и высокой степенью интеграции между компонентами. Именно это делает его особенно ценным в образовательных целях, в государственных и корпоративных проектах, где критичны надёжность, документируемость и предсказуемость поведения системы.

Несмотря на то, что в англоязычной литературе Django часто называют фреймворком по шаблону MTV (Model–Template–View), технически он реализует классическую архитектуру MVC (Model–View–Controller), в которой компонент View отвечает за логику представления данных пользователю, а Controller частично реализуется механизмами маршрутизации и обработчиков запросов внутри фреймворка. В контексте Django View играет роль контроллера, а Template — представления. Терминологическое расхождение носит исторический характер и не влияет на архитектурные свойства фреймворка.


История создания и эволюция версий

Django появился в 2003 году в газете The Lawrence Journal-World (штат Канзас, США), где разработчики Эдриан Холловей (Adrian Holovaty) и Саймон Уиллисон (Simon Willison) создали внутренний инструментарий для ускорения производства новостных веб-проектов. Главной задачей было сократить время от идеи до запуска — отсюда и девиз фреймворка: «The web framework for perfectionists with deadlines».

Первая публичная версия (0.90) была выпущена в июле 2005 года. С тех пор развитие фреймворка стало общественным: к 2008 году — 1.0 (знаменательная версия, ввёдшая чёткий принцип обратной совместимости); к 2013 — 1.6 (поддержка транзакций, улучшенная admin-панель); 1.8 (LTS — длительная поддержка); 2.0 (требование Python 3.5+, упрощение URL-механизмов); 3.0 (асинхронные представления, ASGI); 3.2 (LTS); 4.0 (удаление устаревших API); 4.2 (LTS, до апреля 2026 года); и, наконец, 5.0 — выпущенная в декабре 2023 года, с усилением асинхронных возможностей, обновлённым шаблонизатором, расширением поддержки PostgreSQL-специфичных типов и улучшенной системой миграций.

Каждая LTS-версия (Long-Term Support) получает исправления безопасности и критические фиксы в течение трёх лет, что делает их предпочтительными для промышленного развёртывания.


Архитектура Django

Django строится как многоуровневая система:

  1. Ядро фреймворка — набор базовых модулей, отвечающих за инициализацию, маршрутизацию, обработку исключений, настройки и управление приложениями.
  2. Приложения (apps) — логические модули проекта, изолированные по функциональности. Например, users, blog, payments. Каждое приложение — это самостоятельный Python-пакет со своей структурой.
  3. Слой представления (views) — обработчики HTTP-запросов. Могут быть реализованы как функции (function-based views) или как классы (class-based views, CBV), что позволяет использовать наследование, композицию и полиморфизм.
  4. Шаблонизатор (templates) — система преобразования данных в HTML (или иной текстовый формат). Поддерживает наследование шаблонов, включение, теги, фильтры, автоматическое экранирование.
  5. Модельный слой (models) — описание структуры данных, отображаемой в реляционную СУБД через ORM. Модели определяют поля и поведение (методы экземпляра, менеджеры, пользовательские QuerySet’ы).
  6. Система маршрутизации (URL dispatcher) — механизм сопоставления входящих URL с обработчиками (views) посредством регулярных выражений или path-конвертеров.
  7. Мидлвары (middleware) — цепочка промежуточных обработчиков, перехватывающих запрос до попадания в view и ответ перед отправкой клиенту. Позволяют реализовать аутентификацию, CORS, логирование, кэширование и другие кросс-функциональные задачи.
  8. Система сигналов (signals) — механизм слабосвязанного взаимодействия между компонентами. Аналог событийной модели, но без встроенной гарантии доставки.

Все эти слои работают в едином контексте, управляемом загрузчиком Django (django.setup()), инициируемом при запуске сервера или выполнении команды через manage.py.


Структура Django-проекта

При создании проекта командой django-admin startproject mysite генерируется следующая древовидная структура:

mysite/
├── manage.py
├── mysite/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ ├── wsgi.py
│ └── ...
└── ...

Разберём каждый элемент:

  • manage.py — точка входа для выполнения административных задач: создание приложений, применение миграций, запуск сервера, тестирование. Это скрипт, который устанавливает переменную окружения DJANGO_SETTINGS_MODULE и вызывает django.core.management.execute_from_command_line.

  • __init__.py — пустой файл, обозначающий, что каталог mysite/ является Python-пакетом. Без него импорты не работают.

  • settings.py — центральный конфигурационный файл. Содержит параметры:

    • DEBUG (режим отладки — ни при каких условиях не включать в продакшене),
    • ALLOWED_HOSTS (список доменов, на которых разрешено развёртывание),
    • INSTALLED_APPS (список активных приложений, включая встроенные: django.contrib.admin, django.contrib.auth и т.п.),
    • MIDDLEWARE (цепочка мидлвар),
    • ROOT_URLCONF (путь к главному файлу маршрутов),
    • DATABASES (конфигурация СУБД — по умолчанию SQLite),
    • TEMPLATES, STATIC_URL, STATICFILES_DIRS, MEDIA_URL, SECRET_KEY и многие другие.
  • urls.py — корневой маршрутизатор. Импортирует path, include, и определяет список urlpatterns, в котором каждый элемент связывает путь (например, "", "admin/", "blog/") с обработчиком — либо напрямую с view, либо через делегирование подприложению с помощью include('blog.urls').

  • wsgi.py — точка входа для WSGI-совместимых серверов (Gunicorn, uWSGI). Используется в традиционных синхронных развёртываниях.

  • asgi.py — аналог для ASGI (Asynchronous Server Gateway Interface), необходим для обработки веб-сокетов, long polling и асинхронных view. Основан на стандарте ASGI 3.0.

После создания приложения (python manage.py startapp blog) в нём появляется:

  • apps.py — класс конфигурации приложения (BlogConfig), позволяющий задавать метаданные (имя, метку, готовность к запуску через ready()).

  • models.py — место определения моделей данных. Каждая модель — это класс, наследуемый от django.db.models.Model. Поля объявляются как атрибуты класса.

  • views.py — обработчики запросов. В простейшем случае — функция, принимающая request и возвращающая HttpResponse. В сложных — классы, наследующие View, TemplateView, ListView и прочие.

  • admin.py — регистрация моделей в интерфейсе администратора. Позволяет автоматически генерировать CRUD-интерфейс без написания HTML.

  • tests.py — тесты (рекомендуется выносить в отдельный каталог tests/, но по умолчанию — здесь).

  • migrations/ — каталог, в который makemigrations помещает Python-файлы, описывающие изменения схемы БД. Каждая миграция — это класс, наследуемый от Migration, содержащий operations (список операций вроде CreateModel, AddField) и dependencies.

  • static/ и templates/ — папки для статики (CSS, JS, изображения) и шаблонов соответственно. Django находит их по путям, указанным в STATICFILES_DIRS и DIRS внутри TEMPLATES.

Шаблоны организуются по принципу наследования. Например, base.html определяет общую структуру с {% block content %}{% endblock %}, а blog/post_list.html наследует его и переопределяет блок.


Компоненты Django

Django называют «фреймворком с батарейками», поскольку он включает в себя каркас для маршрутизации, представлений, и полный набор инструментов, достаточных для построения полноценного веб-приложения «из коробки». Эти компоненты органично вшиты в архитектуру, имеют унифицированный API и могут быть отключены или заменены, но их наличие обеспечивает согласованность разработки и снижает порог входа.

ORM (Object-Relational Mapper)

Ядро модельного слоя. Позволяет описывать сущности предметной области на языке Python, не касаясь SQL напрямую. Преобразует операции над объектами (BlogPost.objects.filter(published=True)) в оптимизированные SQL-запросы, адаптированные под конкретную СУБД (PostgreSQL, MySQL, SQLite, Oracle). ORM поддерживает транзакции (atomic), агрегации (Count, Sum, Avg), аннотации, подзапросы, сырой SQL при необходимости (extra, raw), и гарантирует защиту от SQL-инъекций за счёт параметризованных запросов.

Шаблонизатор

Система, построенная на принципах наследования, композиции и декларативного вывода. Шаблоны — это текстовые файлы с синтаксисом тегов ({% ... %}) и переменных ({{ ... }}).

  • {% extends "base.html" %} — наследование.
  • {% block name %}...{% endblock %} — переопределяемые участки.
  • {% include "partial.html" %} — вставка.
  • {% if user.is_authenticated %}...{% endif %} — условная логика (ограниченная — бизнес-логика не должна быть в шаблоне).
  • {{ value|date:"d.m.Y" }} — фильтры (преобразования данных, применяемые в момент рендеринга).

Шаблонизатор автоматически экранирует переменные (<script>&lt;script&gt;), предотвращая XSS. Экранирование можно отключить ({{ html|safe }}), но только при полной уверенности в источнике.

Встроенная админка (django.contrib.admin)

Не просто интерфейс для CRUD — это полностью настраиваемое приложение, построенное на тех же моделях, формах и представлениях, что и пользовательский код. Админка генерирует формы на основе метаданных моделей, учитывает права доступа (User, Group, Permission), поддерживает поиск, фильтрацию, массовые действия. При этом её можно расширять: переопределять шаблоны, добавлять кастомные action’ы, интегрировать сторонние виджеты (например, редакторы Markdown).

Система аутентификации и авторизации (django.contrib.auth)

Реализует стандартные механизмы:

  • Регистрация/вход/выход/сброс пароля.
  • Модели User, Group, Permission.
  • Декораторы (@login_required, @permission_required) и миксины (LoginRequiredMixin).
  • Бэкенды аутентификации (по умолчанию — по логину/паролю, но можно подключить LDAP, OAuth2 через сторонние пакеты).
  • Сессии (django.contrib.sessions), хранящие состояние между запросами (в БД, кэше, файлах).

Система не навязывает единый подход к разграничению доступа: можно использовать проверки на уровне представлений, шаблонов ({% if perms.blog.add_post %}), или даже в ORM (get_queryset() в админке).

REST API и django-rest-framework

Сам Django не включает полноценную поддержку REST — это делает сторонний, но де-факто стандартный пакет Django REST Framework (DRF). Однако ядро предоставляет всё необходимое для его работы: сериализаторы могут опираться на модели, маршрутизация — на URL-конфигурацию, аутентификация — на contrib.auth. DRF добавляет:

  • Сериализаторы (ModelSerializer, HyperlinkedModelSerializer).
  • Классы представлений (APIView, GenericAPIView, ViewSet).
  • Роутеры (SimpleRouter, DefaultRouter).
  • Пагинацию, фильтрацию, документацию (Swagger/OpenAPI).

Без DRF можно построить API вручную — через JsonResponse и @csrf_exempt, но это нарушает принцип DRY и не масштабируется.

Система кэширования (django.core.cache)

Интерфейс абстрагирует конкретные бэкенды:

  • В памяти (LocMemCache) — для разработки.
  • В Redis/Memcached — для продакшена.
  • В файловой системе (редко).

Кэширование может применяться:

  • На уровне всего сайта (CacheMiddleware).
  • На уровне view (@cache_page).
  • На уровне шаблона ({% load cache %}{% cache 600 sidebar %}...{% endcache %}).
  • На уровне данных (cache.set, cache.get).

Поддерживает теги зависимости и автоматическую инвалидацию при использовании cacheops или кастомной логики.

Прочие встроенные компоненты

  • Формы (django.forms) — валидация, приведение типов, рендеринг HTML, защита от CSRF. Умеют работать с моделями (ModelForm).
  • Мидлвары — стандартные: AuthenticationMiddleware, SessionMiddleware, CsrfViewMiddleware, SecurityMiddleware. Позволяют внедрять логику «поперёк» запроса.
  • Интернационализация (i18n) — маркировка строк (gettext, _), переключение языка, форматирование дат/чисел под локаль.
  • Система сигналовpre_save, post_save, m2m_changed. Используется для триггерной логики, но не заменяет прямые вызовы методов.
  • Тестовый клиент (django.test.Client) — имитация HTTP-запросов внутри тестов без запуска сервера.

Эти компоненты не изолированы: админка использует формы, формы опираются на валидацию, валидация может задействовать сигналы, сигналы — писать в кэш. Такая композиция обеспечивает целостность системы.


Маршрутизация

Маршрутизация в Django — это иерархическая система сопоставления, где корневой urls.py делегирует подпути приложениям.

Маршрутизатор (URL dispatcher)

Определяется как список urlpatterns, содержащий объекты path() или re_path().

  • path('blog/<int:year>/', views.year_archive) — использует встроенные конвертеры (int, str, slug, uuid, path).
  • re_path(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive) — для сложных паттернов.

Каждый элемент маршрута может:

  • Напрямую указывать на функцию/класс (views.PostList).
  • Делегировать поддерево через include('blog.urls').
  • Иметь имя (name='post_detail'), чтобы генерировать URL в коде или шаблонах ({% url 'post_detail' pk=5 %}).

При запросе GET /blog/2024/ Django:

  1. Ищет совпадение в ROOT_URLCONF.
  2. Находит path('blog/', include('blog.urls')).
  3. Передаёт остаток пути (2024/) в blog/urls.py.
  4. Там сопоставляет с path('<int:year>/', views.archive).
  5. Вызывает views.archive(request, year=2024).

Это — диспетчеризация, а не простое регулярное выражение: порядок маршрутов важен (первое совпадение выигрывает), и система не допускает неоднозначности без явного указания.


Жизненный цикл разработки

Процесс создания приложения в Django строго регламентирован — это гарантия воспроизводимости.

  1. Создание проекта

    django-admin startproject mysite
    cd mysite
  2. Создание приложения

    python manage.py startapp blog

    Затем добавляем 'blog' в INSTALLED_APPS.

  3. Настройка базы данных
    В settings.py:

    DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.postgresql',
    'NAME': 'mydb',
    'USER': 'myuser',
    'PASSWORD': 'mypass',
    'HOST': 'localhost',
    'PORT': '5432',
    }
    }

    (Для разработки допустим sqlite3, но не в продакшене.)

  4. Определение моделей
    В blog/models.py:

    from django.db import models

    class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

    class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
  5. Генерация и применение миграций

    python manage.py makemigrations
    python manage.py migrate

    Команда makemigrations анализирует изменения в моделях и создаёт Python-файл в blog/migrations/0001_initial.py. migrate применяет его к БД.

  6. Создание представлений
    В blog/views.py:

    from django.shortcuts import render
    from .models import Post

    def post_list(request):
    posts = Post.objects.filter(published=True).order_by('-created_at')
    return render(request, 'blog/post_list.html', {'posts': posts})
  7. Настройка маршрутов
    blog/urls.py:

    from django.urls import path
    from . import views

    urlpatterns = [
    path('', views.post_list, name='post_list'),
    ]

    mysite/urls.py:

    from django.urls import include, path

    urlpatterns = [
    path('blog/', include('blog.urls')),
    path('admin/', admin.site.urls),
    ]
  8. Шаблоны
    blog/templates/blog/post_list.html:

    {% extends "base.html" %}

    {% block content %}
    <h1>Публикации</h1>
    <ul>
    {% for post in posts %}
    <li>{{ post.title }} ({{ post.created_at|date:"d.m.Y" }})</li>
    {% empty %}
    <li>Нет опубликованных записей.</li>
    {% endfor %}
    </ul>
    {% endblock %}
  9. Запуск сервера разработки

    python manage.py runserver

Сервер runserverне для продакшена. Он однопоточный, не обрабатывает статику в DEBUG=False, и не масштабируется. Для развёртывания используют Gunicorn + Nginx или Daphne (для ASGI).


ORM

Типы полей модели

Каждое поле — экземпляр класса Field, определяющий:

  • Как данные хранятся в БД.
  • Как они валидируются.
  • Как они конвертируются в Python-объект.

Ключевые типы:

  • CharField(max_length=255) — строка фиксированной длины (VARCHAR в SQL). Обязателен max_length.
  • TextField() — длинный текст (TEXT). Без ограничения длины.
  • EmailField() — валидация по формату email.
  • GenericIPAddressField(protocol='both') — IPv4/IPv6.
  • SlugField() — строка, пригодная для URL (латиница, цифры, дефисы). Часто используется вместе с prepopulated_fields в админке.
  • URLField() — валидация URL.
  • UUIDField() — уникальный идентификатор (используется как первичный ключ вместо AutoField в распределённых системах).
  • AutoField() / BigAutoField() — автоинкрементный целочисленный PK (по умолчанию для id).
  • IntegerField(), BigIntegerField(), SmallIntegerField() — целые числа разного диапазона.
  • DecimalField(max_digits=10, decimal_places=2) — точная десятичная дробь (для денег).
  • FloatField() — приблизительное число с плавающей точкой (IEEE 754).
  • DateField(), TimeField(), DateTimeField() — дата, время, дата+время.
    • auto_now=True — обновляется при save().
    • auto_now_add=True — устанавливается один раз при создании.
  • DurationField() — разница во времени (timedelta в Python, INTERVAL в PostgreSQL).
  • FileField(upload_to='uploads/'), ImageField() — загрузка файлов. ImageField дополнительно проверяет MIME-тип и размеры.
  • BinaryField() — сырые байты (BLOB).
  • JSONField() — хранение структурированных данных (только в PostgreSQL, MySQL 5.7+, SQLite 3.38+).

Поля могут иметь параметры: null, blank, default, choices, help_text, verbose_name.

Методы QuerySet API

ORM возвращает QuerySet — ленивый, цепляемый объект. Выполнение SQL происходит при итерации, срезе, len(), list().

  • filter(**kwargs) — выборка по условиям.

    Post.objects.filter(published=True, author__name='Иван')
    # WHERE published = TRUE AND author.name = 'Иван'
  • get(**kwargs) — получение одного объекта. Выбрасывает DoesNotExist или MultipleObjectsReturned.

    post = Post.objects.get(slug='hello-world')
  • create(**kwargs) — создание и сохранение за один вызов.

    Post.objects.create(title='Новая запись', content='...', author=author)
  • update(**kwargs) — массовое обновление (без вызова save(), без сигналов pre_save).

    Post.objects.filter(published=False).update(published=True)
  • delete() — массовое удаление.

    Post.objects.filter(created_at__lt='2020-01-01').delete()
  • order_by('field'), distinct(), values(), values_list(), annotate(), aggregate() — для сортировки, дедупликации, выборки словарей/кортежей, расчёта агрегатов.

ORM поддерживает связки:

  • ForeignKeyauthor__name (двойное подчёркивание для перехода через связь).
  • ManyToManyFieldpost__tags__name.
  • select_related() — JOIN для ForeignKey (оптимизация N+1).
  • prefetch_related() — отдельный запрос для ManyToMany и обратных связей.

Миграции

Миграции — это код, управляющий структурой БД. Они решают две задачи:

  1. История изменений (кто, когда, что изменил в схеме).
  2. Воспроизводимость (новый разработчик или сервер могут достичь актуального состояния БД одной командой).

Процесс:

  1. Меняется models.py.
  2. makemigrations сравнивает текущее состояние моделей с последней миграцией и генерирует Python-файл.
  3. migrate применяет миграции в порядке зависимостей.

Миграции — необратимы по умолчанию. --fake и --fake-initial используются только в чрезвычайных ситуациях. Для отката пишут reverse_code вручную или используют migrate <app> <previous_migration>.

Сложные сценарии:

  • Данные в миграции — через RunPython.
    def populate_authors(apps, schema_editor):
    Author = apps.get_model('blog', 'Author')
    Author.objects.create(name='Админ', email='admin@example.com')
  • Разделение миграций — когда одна модель влияет на другую, но приложения независимы.
  • Совместимость — миграции должны быть идемпотентными и не ломать работающие приложения при развёртывании «на лету».

Админка Django

Как получить доступ

  1. Создать суперпользователя:
    python manage.py createsuperuser
  2. Запустить сервер: python manage.py runserver.
  3. Перейти по /admin/, войти.

Как это работает «под капотом»

Админка — это Django-приложение, зарегистрированное как django.contrib.admin.

  • admin.site — глобальный инстанс AdminSite.
  • admin.site.register(Model, ModelAdmin) — связывает модель с её конфигурацией.
  • ModelAdmin — класс, определяющий:
    • Какие поля отображать (list_display, list_filter).
    • Как искать (search_fields).
    • Как редактировать (fields, fieldsets, readonly_fields).
    • Как валидировать (clean()).
    • Как обрабатывать массовые действия (actions).

Пример настройки:

# blog/admin.py
from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'published', 'created_at')
list_filter = ('published', 'created_at')
search_fields = ('title', 'content')
date_hierarchy = 'created_at'
prepopulated_fields = {'slug': ('title',)}
actions = ['make_published']

def make_published(self, request, queryset):
updated = queryset.update(published=True)
self.message_user(request, f'{updated} записей опубликовано.')
make_published.short_description = "Опубликовать выбранные записи"

Админка использует те же шаблоны, что и пользовательские приложения (admin/base_site.html можно переопределить), те же формы (ModelForm), и те же сигналы. Это позволяет, например:

  • Добавить в админку график статистики через кастомный view и шаблон.
  • Интегрировать внешний API при сохранении объекта.
  • Ограничить видимость записей по правам пользователя (get_queryset()).

Важно: админка — не интерфейс для конечного пользователя. Она предназначена для управления контентом и конфигурацией. Для публичных интерфейсов строятся отдельные представления.


Асинхронность в Django

Django изначально создавался как синхронный фреймворк. Поддержка асинхронности появилась постепенно, начиная с версии 3.0, и реализована как надстройка над существующей архитектурой. Это принципиально: асинхронность в Django — инструмент для конкретных задач.

Что стало возможным

  1. Асинхронные представления (async views)

    async def async_view(request):
    data = await fetch_external_api() # I/O-bound операция
    return JsonResponse({'data': data})

    Такие view обрабатываются ASGI-сервером (Daphne, Uvicorn) без блокировки потока на время ожидания. Однако:

    • ORM по-прежнему синхронный. Вызов await Post.objects.afilter(...) возможен только в асинхронном контексте и требует asyncpg (PostgreSQL) или совместимого бэкенда.
    • Синхронный код внутри async-view блокирует весь цикл событий — его следует оборачивать в sync_to_async.
  2. ASGI-совместимость
    Файл asgi.py позволяет развёртывать приложение на серверах, поддерживающих асинхронные протоколы:

    • HTTP/1.1 и HTTP/2 — обычные запросы.
    • Веб-сокеты — через channels (официальный проект Django).
  3. channels — расширение для событийной модели
    Не входит в ядро, но рекомендован разработчиками Django. Позволяет:

    • Обрабатывать веб-сокеты (AsyncWebsocketConsumer).
    • Организовывать фоновые задачи через channel layers (Redis, In-Memory).
    • Строить чаты, уведомления в реальном времени.
      Важно: channels не заменяет Celery — он для короткоживущих, связанных с соединением событий. Долгие задачи (генерация отчётов, обработка видео) — в Celery или RQ.

Ограничения и компромиссы

  • ORM остаётся частично синхронным. Полностью асинхронный доступ к данным (Post.objects.acreate(...)) доступен, но требует явного использования асинхронных методов и поддержки СУБД.
  • Мидлвары должны быть помечены как sync/async. Смешивание вызывает SynchronousOnlyOperation.
  • Производительность не растёт автоматически. Асинхронность эффективна при высокой доле I/O (внешние API, медленные БД), но ухудшает performance при CPU-bound операциях.
  • Сложность отладки возрастает. Стек вызовов раздваивается: sync/async boundary требует явного преобразования (sync_to_async, async_to_sync).

Таким образом, асинхронность в Django — целенаправленный выбор для решения конкретных проблем масштабируемости. Для большинства CRUD-приложений, особенно в госсекторе или корпоративных системах, синхронный стек остаётся предпочтительным: он проще, стабильнее и лучше документирован.


Безопасность

Django включает широкий набор защит по умолчанию, но никакой фреймворк не делает приложение безопасным автоматически.

Защита по умолчанию

  • CSRF-токены — включены через CsrfViewMiddleware. Формы в шаблонах должны содержать {% csrf_token %}. REST API (особенно stateless) требуют отключения или замены на другие методы (например, JWT).
  • XSS-защита — автоматическое экранирование в шаблонах. Риск возникает только при использовании |safe, mark_safe(), или ручном формировании HTML.
  • SQL-инъекции — исключены при использовании ORM и параметризованных запросов. Риск — при extra(), raw() без экранирования.
  • Clickjacking — блокируется заголовком X-Frame-Options: DENY (настраивается через XFrameOptionsMiddleware).
  • Безопасные заголовкиSecurityMiddleware добавляет:
    • HTTP Strict Transport Security (HSTS)
    • Content Security Policy (CSP)требует ручной настройки
    • Referrer-Policy, X-Content-Type-Options, X-XSS-Protection (устарел, но иногда нужен для старых браузеров).

Что требует явной настройки

  • Content Security Policy (CSP) — не включён по умолчанию. Для активации нужен django-csp или ручное управление заголовками. Без CSP даже экранирование не спасает от атак через script-src 'unsafe-inline'.
  • Rate limiting — отсутствует в ядре. Реализуется через Nginx, Redis + django-ratelimit, или в мидлваре.
  • Двухфакторная аутентификация — через сторонние пакеты (django-otp, django-two-factor-auth).
  • Логирование безопасностиSECURE_CONTENT_TYPE_NOSNIFF, SECURE_BROWSER_XSS_FILTER устарели; современный подход — CSP + report-uri.

Критические ошибки разработчиков

  1. Отключение DEBUG = True в продакшене (раскрывает стектрейсы, настройки, SQL).
  2. Использование SECRET_KEY в репозитории.
  3. Хранение паролей в открытом виде (ORM не шифрует — только хэширует через PBKDF2/HMAC-SHA256).
  4. Доверие к request.META['HTTP_X_FORWARDED_FOR'] без валидации (подмена IP).
  5. Неправильная настройка CORS — особенно при использовании DRF.

Безопасность в Django — оборонительная стратегия: фреймворк ставит барьеры по умолчанию, но окончательная ответственность лежит на разработчике.


Тестирование

Django предоставляет мощную тестовую инфраструктуру на базе unittest, расширенную собственными утилитами.

Уровни тестирования

  1. Модульные тесты (TestCase)

    • Тестируют модели, формы, вспомогательные функции.
    • Используют in-memory SQLite (быстро, изолированно).
    • Пример:
      from django.test import TestCase
      from blog.models import Post

      class PostModelTest(TestCase):
      def test_published_posts(self):
      Post.objects.create(title='Черновик', published=False)
      Post.objects.create(title='Публикация', published=True)
      self.assertEqual(Post.objects.published().count(), 1)
  2. Интеграционные тесты (TransactionTestCase)

    • Проверяют взаимодействие компонентов: view + модель + шаблон.
    • Сохраняют транзакции между тестами (медленнее).
  3. Тесты клиентского уровня (Client, RequestFactory)

    • Client — имитация HTTP-запросов:
      response = self.client.get('/blog/')
      self.assertEqual(response.status_code, 200)
      self.assertContains(response, 'Публикации')
    • RequestFactory — создаёт объект HttpRequest без запуска middleware (для тестирования view в изоляции).
  4. Тесты шаблонов

    • Проверка контекста, использования блоков, фильтров.
    • response.templates, response.context.

Особенности

  • База данных создаётся заново для каждого TestCase.
  • --keepdb ускоряет повторные запуски.
  • pytest-django — альтернатива стандартному runner’у, с поддержкой fixture, параметризации, асинхронных тестов.
  • Тесты админки: admin.site.urls доступен, можно проверять рендеринг форм, права.
  • Покрытие: coverage run manage.py test && coverage report.

Тестирование в Django — не опционально. Это часть жизненного цикла: миграции, админка, формы — всё ломается при несогласованности изменений. Регулярный запуск тестов — условие поддерживаемости.


Развёртывание

Этапы развёртывания

  1. Подготовка окружения

    • DEBUG = False
    • ALLOWED_HOSTS = ['example.com', 'www.example.com']
    • SECRET_KEY — из переменной окружения (os.getenv('SECRET_KEY')).
    • Настройка логирования (LOGGING в settings.py).
  2. Статические файлы

    python manage.py collectstatic

    Собирает все static/ в STATIC_ROOT. Обслуживаются веб-сервером (Nginx):

    location /static/ {
    alias /path/to/staticfiles/;
    }
  3. Медиафайлы
    Аналогично, но с учётом безопасности:

    • Ограничение MIME-типов.
    • Хранение вне корня проекта.
    • Использование signed URLs (через django-storages + S3).
  4. Сервер приложения

    • WSGI: Gunicorn (предпочтительно) + Nginx.
      gunicorn mysite.wsgi:application --bind 0.0.0.0:8000 --workers 3
    • ASGI: Daphne/Uvicorn — только при использовании веб-сокетов или async views.
  5. База данных

    • PostgreSQL — рекомендуемый выбор (поддержка JSONField, ARRAY, HSTORE, надёжность).
    • Резервное копирование (pg_dump), мониторинг, connection pooling (pgBouncer).
  6. Мониторинг и логирование

    • Sentry — для отслеживания исключений.
    • Prometheus + Grafana — метрики (запросы/сек, время ответа).
    • ELK-stack — агрегация логов.

Контейнеризация

Docker не обязателен, но упрощает воспроизводимость:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
CMD ["gunicorn", "mysite.wsgi:application", "--bind", "0.0.0.0:8000"]

Важно: не запускать миграции в Dockerfile — только в entrypoint.sh при старте контейнера.


Типичные антипаттерны и как их избежать

  1. «Гигантский views.py»
    — Признак: тысячи строк, смесь логики, шаблонов, API.
    — Решение:

    • Вынос бизнес-логики в services.py.
    • Использовать классы (ListView, DetailView).
    • Разделять на модули: views/list.py, detail.py, api.py.
  2. N+1 запросов
    — Признак: post.author.name в цикле без select_related.
    — Решение:

    • select_related() для ForeignKey.
    • prefetch_related() для ManyToMany.
    • django-debug-toolbar для выявления.
  3. Логика в шаблонах
    — Признак: {% if user.is_staff or user.is_superuser or post.author == user %}.
    — Решение:

    • Вынос в метод модели (post.can_edit(user)).
    • Использование TemplateView с предварительно вычисленным контекстом.
  4. Жёсткая привязка к админке
    — Признак: «Пользователи редактируют контент только через /admin/».
    — Решение:

    • Построение пользовательских форм и интерфейсов.
    • Админка — только для технических администраторов.
  5. Игнорирование миграций
    — Признак: «Просто написал SQL вручную в pgAdmin».
    — Решение:

    • Всегда использовать makemigrations.
    • Проверка миграций в CI (python manage.py makemigrations --check --dry-run).

Сравнение Django с Flask, FastAPI и Pyramid

Выбор фреймворка — инженерное решение, основанное на требованиях к проекту, а не на хайпе. Django не «лучше» или «хуже» — он другой.

КритерийDjangoFlaskFastAPIPyramid
Тип«Батарейки в комплекте», монолитныйМикрофреймворк, минимализмСовременный, API-firstГибкий, настраиваемый
АрхитектураЖёсткая структура (apps, models, views)Свободная (любая организация кода)Слабо структурирован (но рекомендуется routers, schemas)Явная декларация компонентов
ORMВстроенный (Django ORM)Нет (обычно SQLAlchemy)Нет (обычно SQLAlchemy или Tortoise)Нет (обычно SQLAlchemy)
АдминкаДа, с полной кастомизациейНет (Flask-Admin — отдельный пакет)НетНет
ВалидацияВ формах и моделяхРучная или через MarshmallowАвтоматическая (Pydantic)Через Colander или Marshmallow
АсинхронностьЧастичная (с версии 3.0)Через Quart или Flask + asyncioПолная (на базе Starlette)Частичная (через pyramid_asyncio)
Документация APIТребует DRF + drf-yasg/SwaggerТребует сторонних инструментовАвтоматическая (OpenAPI/Swagger UI)Требует ручной настройки
Скорость разработкиВысокая (MVP за часы)Средняя (нужно собирать стек)Высокая для APIСредняя (требует проектирования)
Скорость выполненияУмеренная (накладные расходы каркаса)Высокая (минимум overhead)Очень высокая (асинхронность + Pydantic)Умеренная
Типичное применениеКорпоративные системы, CMS, порталыМикросервисы, прототипы, скриптыAPI, микросервисы, ML-инференсEnterprise-приложения, legacy-интеграция

Когда выбирать Django?

  • Требуется единая точка управления (админка + пользовательский интерфейс + API).
  • Проект ориентирован на данные и бизнес-логику, а не на высоконагруженные API.
  • Важна предсказуемость и сопровождаемость (госзаказ, финансовые системы).
  • Команда включает начинающих разработчиков — структура ускоряет онбординг.

Когда НЕ выбирать Django?

  • Чистый REST/gRPC-сервис без веб-интерфейса.
  • Высокая нагрузка на чтение (десятки тысяч RPS) — тогда FastAPI + кэш.
  • Интеграция с legacy-системами через нестандартные протоколы (тогда Flask + адаптеры).
  • Требуется полная свобода выбора ORM (например, переход с SQLAlchemy на Tortoise).

Django — консервативный, проверенный временем выбор для систем, где важнее надёжность и скорость старта, чем предельная производительность.


Расширение через сигналы и middleware

Django позволяет внедрять логику «поперёк» архитектуры без изменения ядра. Это критично для масштабирования и поддержки.

Сигналы: слабосвязанное взаимодействие

Сигналы — аналог событийной модели. Основные встроенные сигналы:

  • pre_save / post_save — перед/после сохранения объекта.
  • pre_delete / post_delete — перед/после удаления.
  • m2m_changed — при изменении ManyToMany-связи.
  • request_started / request_finished — начало/окончание обработки запроса.

Пример: инвалидация кэша при обновлении поста:

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.cache import cache
from .models import Post

@receiver(post_save, sender=Post)
def invalidate_post_cache(sender, instance, **kwargs):
cache.delete(f'post_detail_{instance.pk}')
cache.delete('post_list')

Ограничения сигналов:

  • Не гарантируют порядок выполнения.
  • Не работают при массовом обновлении (QuerySet.update()).
  • Сложно тестировать и отлаживать (неявные зависимости).
  • Не заменяют прямые вызовы методов — использовать только там, где нельзя изменить вызывающий код (например, в сторонних приложениях).

Middleware

Мидлвар — класс с методами __call__ или process_request/process_response. Выполняется в порядке, указанном в MIDDLEWARE.

Пример: логирование времени обработки:

import time
from django.utils.deprecation import MiddlewareMixin

class TimingMiddleware(MiddlewareMixin):
def process_request(self, request):
request.start_time = time.time()

def process_response(self, request, response):
duration = time.time() - request.start_time
response['X-Response-Time'] = f'{duration:.3f}s'
return response

Встроенные мидлвары, которые стоит понимать:

  • SecurityMiddleware — безопасные заголовки.
  • CsrfViewMiddleware — защита от CSRF.
  • SessionMiddleware — загрузка сессии из хранилища.
  • AuthenticationMiddleware — привязка request.user.
  • MessageMiddleware — одноразовые сообщения («Запись сохранена»).

При написании кастомного middleware:

  • Избегать тяжёлых операций в process_request (замедляет все запросы).
  • Обрабатывать исключения в process_exception.
  • Для асинхронных view — наследоваться от AsyncMiddlewareMixin.

Сигналы и middleware — инструменты ответственности: их чрезмерное использование превращает проект в «лазанью» неявных зависимостей.


Интернационализация (i18n) и локализация (l10n)

Django предоставляет полную поддержку многоязычности — от маркировки строк до форматирования дат под локаль.

Этапы внедрения

  1. Маркировка строк
    В коде:

    from django.utils.translation import gettext as _
    title = _('Welcome to our site')

    В шаблонах:

    {% load i18n %}
    <h1>{% trans "Welcome to our site" %}</h1>
  2. Генерация .po-файлов

    django-admin makemessages -l ru

    Создаёт locale/ru/LC_MESSAGES/django.po — файл перевода.

  3. Компиляция в .mo

    django-admin compilemessages
  4. Переключение языка

    • Через URL (/ru/blog/, /en/blog/ с i18n_patterns).
    • Через заголовок Accept-Language.
    • Через куки/сессию (set_language redirect view).

Особенности

  • Форматирование дат/чисел — зависит от USE_L10N = True и LANGUAGE_CODE.
    Например, {{ date|date:"SHORT_DATE_FORMAT" }}05.12.2025 (ru) vs 12/05/2025 (en).
  • Множественные формы — через ngettext:
    ngettext('%(count)d post', '%(count)d posts', count) % {'count': count}
  • Перевод моделей — требует сторонних пакетов (django-modeltranslation, django-parler).
    В ядре — только перевод строк интерфейса, не данных.

Интернационализация в Django — зрелая, промышленная система, но требует дисциплины: каждая строка, видимая пользователю, должна быть помечена. Пропуск одной строки ломает весь перевод.


Работа с legacy-кодом

Django часто используется для модернизации старых систем. Ключевые стратегии:

1. Постепенная замена (Strangler Fig Pattern)

  • Новый функционал пишется на Django.
  • Старая система остаётся, но новые URL перехватываются Django.
  • Пример:
    • Старый /old-crm/ — обслуживается PHP.
    • Новый /crm/ — на Django.
    • Постепенно /old-crm/* перенаправляются или реверс-проксируются.

2. Обёртка вокруг legacy-логики

  • Django выступает как «тонкий» фасад:
    def legacy_report_view(request):
    # Вызов старого скрипта через subprocess или HTTP
    result = requests.post('http://legacy/report', json=request.POST)
    return JsonResponse(result.json())

3. Совместное использование базы данных

  • Django ORM работает с существующей схемой:
    class LegacyUser(models.Model):
    login = models.CharField(max_length=50)
    # ... поля без primary_key=True, если PK не `id`
    class Meta:
    db_table = 'users_old'
    managed = False # Django не создаёт/не меняет таблицу
  • managed = False — критично: миграции не трогают эту таблицу.

4. Интеграция через API

  • Legacy-система предоставляет REST/SOAP.
  • Django использует requests, zeep (для SOAP), кэширование, retry-логику.
  • Для SOAP — django-soap или ручная обработка через xml.etree.

5. Тестирование legacy-интеграций

  • Mock внешних вызовов (unittest.mock.patch).
  • Запись/воспроизведение запросов (vcr.py).
  • Проверка идемпотентности (повторный вызов не ломает данные).

Legacy-интеграция — организационная задача: необходимо документировать границы ответственности, точки отказа и процедуры отката.